<?php

namespace lc\wechat\auth\Gateways;

use lc\cache\redis\Redis;
use lc\helpers\Curl;
use lc\helpers\Helper;
use lc\Http;
use think\facade\Db;

/**
 * @desc    微信小程序类
 * @package lc\wechat\auth\Gateways
 */
class Mini extends Base
{
    private $appId;
    private $secret;

    public function __construct()
    {
        parent::__construct();

        $this->appId  = $this->config['appid'];
        $this->secret = $this->config['secret'];
    }

    /**
     * @link    https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/login/auth.code2Session.html
     * @desc    使用授权code换取openid
     * @param   string $code
     * @return  array
     */
    public function code2Openid(string $code): array
    {
        $url = 'https://api.weixin.qq.com/sns/jscode2session';
        $res = Curl::requestCurl($url, 'GET', [], http_build_query([
            'appid'      => $this->appId,
            'secret'     => $this->secret,
            'js_code'    => $code,
            'grant_type' => 'authorization_code'
        ]));

        return $res ? json_decode($res, true) : [];
    }

    /**
     * @link    https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/signature.html
     * @desc    解密小程序信息
     * @param   string $sessionKey      session_key
     * @param   string $encryptedData   加密字符
     * @param   string $iv              偏移量
     * @return  array
     */
    public function decrypt($sessionKey, $encryptedData, $iv): array
    {
        if (strlen($iv) != 24) {
            // 偏移量错误
            return ['code' => 41002, 'data' => '', 'msg'  => 'iv解密失败'];
        }
        if (strlen($sessionKey) != 24) {
            // sessionkey 错误
            return ['code' => 41001, 'data' => '', 'msg'  => 'sessionKey解密失败'];
        }

        $data = openssl_decrypt(base64_decode($encryptedData), 'AES-128-CBC', base64_decode($sessionKey), OPENSSL_RAW_DATA, base64_decode($iv));
        if (!$data) {
            return ['code' => 41001, 'data' => '', 'msg'  => '解密失败'];
        }
        $userInfo = json_decode($data, true);
        if ($userInfo['watermark']['appid'] != $this->appId) {
            return ['code' => 41003, 'data' => '', 'msg'  => 'appid不匹配'];
        }

        $userInfo['nickName'] = !empty($userInfo['nickName']) ? $userInfo['nickName'] : '';
        return ['code' => 0, 'data' => $userInfo, 'msg'  => 'success'];
    }

    /**
     * @link    https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/qr-code/wxacode.createQRCode.html
     * @desc    获取小程序码(限量)
     * @param   string  $path   页面路径
     * @param   int     $width  二维码宽度
     * @return  array
     */
    public function qrcode(string $path, $width = 430)
    {
        $params = ['path' => $path, 'width' => $width];
        $url = 'https://api.weixin.qq.com/cgi-bin/wxaapp/createwxaqrcode?access_token=' . $this->accessToken();
        return Curl::requestCurl($url, 'POST', [], json_encode($params));
    }

    /**
     * @link    https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/qr-code/wxacode.getUnlimited.html
     * @desc    获取小程序码(不限量)
     * @param   string  $scene 场景值
     * @param   array   $extra 额外参数,详见
     * @return  bool|mixed|string
     */
    public function qrcodeUnlimited(string $scene, $extra = [])
    {
        $params = array_merge($extra, ['scene' => $scene]);
        $url = 'https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token=' . $this->accessToken();
        return Curl::requestCurl($url, 'POST', [], json_encode($params));
    }

    /**
     * @link    https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/url-link/urllink.generate.html
     * @desc    生成短链接
     * @param   array $params
     * @return  bool|string
     */
    public function urlLink(array $params)
    {
        $url = 'https://api.weixin.qq.com/wxa/generate_urllink?access_token=' . $this->accessToken();
        return Curl::requestCurl($url, 'POST', [], json_encode($params));
    }

    /**
     * @link    https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/subscribe-message/subscribeMessage.send.html
     * @desc    发送模板消息
     * @param   string  $openid     小程序openid
     * @param   string  $template   模板ID
     * @param   array   $data       小程序数据
     * @param   string  $page       跳转页面
     * @param   string  $state      小程序状态
     * @return  bool|mixed
     */
    public function sendMiniMsg(string $openid, string $template, array $data, string $page = null, string $state = 'normal'): array
    {
        $params = [
            'touser'        => $openid,
            'template_id'   => $template,
            'miniprogram_state' => $state,
            'data'          => $data
        ];
        if ($page) {
            $params['page'] = $page;
        }
        $url = 'https://api.weixin.qq.com/cgi-bin/message/subscribe/send?access_token=' . $this->accessToken();
        $res = Curl::requestCurl($url, 'POST', [], json_encode($params));

        return $res ? json_decode($res, true) : [];
    }

    /**
     * @link    https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/access-token/auth.getAccessToken.html
     * @desc    获取全局唯一接口调用凭据
     * @param   int $repeatCnt
     * @return  string
     */
    public function accessToken(&$repeatCnt = 0)
    {
        $drive = $this->config['token_drive'] ?? 'redis';
        $key   = 'mini_' . $this->appId . '_accessToken';
        switch ($drive) {
            case 'redis':
                $redis = Redis::instance();
                $redis->select(15);

                $token = $redis->get($key);
                break;
            case 'db':
            default:
                $token = Db::connect('main')->name('configs')->where('c_key', $key)->where('c_expired > ' . time())->value('c_value');
                break;
        }
        if ($token) {
            return $token;
        }

        $url = 'https://api.weixin.qq.com/cgi-bin/token';
        $res = Curl::requestCurl($url, 'GET', [], http_build_query([
            'grant_type' => 'client_credential',
            'appid'      => $this->appId,
            'secret'     => $this->secret
        ]));

        $data = json_decode($res, true);
        if (isset($data['access_token'])) {
            $token = $data['access_token'];
            switch ($drive) {
                case 'redis':
                    $redis->setex($key, 7000, $token);
                    break;
                case 'db':
                default:
                    Db::connect('main')
                        ->name('configs')
                        ->where('c_key', $key)
                        ->update([
                            'c_value'   => $token,
                            'c_expired' => time() + 7000,
                            'c_updated' => date('Y-m-d H:i:s')
                        ]);
                    break;
            }

            return $token;
        } else {
            if ($repeatCnt <= 3) {
                $repeatCnt++;
                return $this->accessToken($repeatCnt);
            } else {
                return $data['errcode'];
            }
        }
    }
}
